﻿using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using IBNet.UtilsDataStructuresAndExtensions;

namespace IBNet.DayTrade
{
   public partial class DayTradeMainForm : Form, IWrapDayTradeMainForm
   {
      void client_ExecutionDetailsReceived(object sender, ExecutionDetailsEventArgs e)
      {
         if (_openingOrderIds.Contains(e.OrderId))
         {
            SubmitBracketOrders(e);

            // Remove from _openingOrderIds so that multiple partial fills 
            // of the opening order do not cause multiple sets of 
            // bracket orders to be submitted.
            _openingOrderIds.Remove(e.OrderId);
         }
      }

      void SubmitBracketOrders(ExecutionDetailsEventArgs e)
      {
         int daytradeOcaNumber = ++DayTradeProgram.DaytradeOcaNumber;
         string ocaGroup = e.Contract.FullSymbol + daytradeOcaNumber.ToString();

         Order targetOrder = BracketOrderFactory.NewTargetOrder(e, ocaGroup);
         Order trailingStopOrder = BracketOrderFactory.NewTrailingStopOrder(e, ocaGroup);
         Order timeStopOrder = BracketOrderFactory.NewTimeStopOrder(e, ocaGroup);

         IBClientWrapper.SubmitOrder(targetOrder, e.Contract);
         IBClientWrapper.SubmitOrder(trailingStopOrder, e.Contract);
         IBClientWrapper.SubmitOrder(timeStopOrder, e.Contract);
      }

      void client_RealTimeBarReceived(object sender, RealTimeBarEventArgs e)
      {
         PriceBarsAndIndicatorValues priceBarsAndIndicatorValues =
            _requestIdToPriceBarsAndIndicatorValues[e.RequestId];

         listViewRealTimeBarSubscriptions.BeginInvoke((MethodInvoker)delegate
         {
            UpdateRealTimeBarsListView(priceBarsAndIndicatorValues, e);
         });

         PriceBar priceBar = new PriceBar(e.Open, e.High, e.Low, e.Close,
            e.Volume, e.OpenTimeSince1970, e.OpenTimeSince1970.ConvertTo1970DateTime());

         if (priceBarsAndIndicatorValues.IsInNeedOfBackfill)
         {
            // Delay backfilling until at a bar-completing open time so that when Backfill 
            // is called all the needed data will be available.
            if (priceBarsAndIndicatorValues.PriceBarIsBarCompleting(priceBar))
            {
               Backfill(priceBarsAndIndicatorValues);
            }
            else
	         {
               return;
         	}
         }

         // Keep returning until real time price bars have caught up with backfill.
         // The body of this if-statement is usually called 2-3 times after a backfill.
         if (!priceBarsAndIndicatorValues.
             OpenTimeIsAtLeastFiveSecondsAheadOfPreviousOpenTime(e.OpenTimeSince1970))
         {
            return;
         }

         priceBarsAndIndicatorValues.PushFrontFiveSecondBar(priceBar);

         // Verification purposes
         textBoxTesting1.WriteLine("========= " + priceBarsAndIndicatorValues.FullSymbol +
            " Price Bars after PushFront=========");
         textBoxTesting1.WriteLine(DateTime.Now.ToLongTimeString() + " == DateTime.Now");
         foreach (PriceBar priceBar1 in priceBarsAndIndicatorValues.PriceBars)
         {
            textBoxTesting1.WriteLine(priceBar1.ToString());
         }

         if (priceBarsAndIndicatorValues.PriceBarIsBarCompleting(priceBar))
         {
            if (!HaveOpenDayTradeOrderWith(priceBarsAndIndicatorValues.FullSymbol))
            {
               ApplyStrategy(priceBarsAndIndicatorValues);
            }
         }
      }

      private void Backfill(PriceBarsAndIndicatorValues priceBarsAndIndicatorValues)
      {
         int backfillRequestId = Interlocked.Increment(ref _currentRequestId);
         _backfillRequestIdToPriceBarsAndIndicatorValues.Add(
            backfillRequestId, priceBarsAndIndicatorValues);

         Contract contract = priceBarsAndIndicatorValues.Contract;
         HistoricalDataType whatToShow = (contract.SecurityType == SecurityType.Cash) ?
            HistoricalDataType.Midpoint : HistoricalDataType.Trades;

         TimeSpan durationOfDataToRequest
            = priceBarsAndIndicatorValues.DurationOfDataToRequestWhenBackfilling;

         ibClient.RequestHistoricalData(backfillRequestId, contract, durationOfDataToRequest,
            DateTime.Now, priceBarsAndIndicatorValues.HistoricalBarSizeWhenBackfilling,
            whatToShow, TradingHours.AllHours);

         if (!_backfillCompleted.WaitOne(3000))
         {
            textBoxMessages.WriteLine("Error: ",
               priceBarsAndIndicatorValues.FullSymbol,
               " backfill failed to finish within 3 seconds.");
         }

         // Verification purposes
         textBoxTesting1.WriteLine("====== "
            + priceBarsAndIndicatorValues.FullSymbol
            + " Price Bars After Backfill======");
         foreach (PriceBar priceBar1 in priceBarsAndIndicatorValues.PriceBars)
         {
            textBoxTesting1.WriteLine(priceBar1.ToString());
         }
      }

      private void ApplyStrategy(PriceBarsAndIndicatorValues priceBarsAndIndicatorValues)
      {
         StrategyResult strategyResult = ApplyStrategyLogic(priceBarsAndIndicatorValues);
         if (strategyResult != StrategyResult.NoTrade)
         {
            ActionSide openingOrderActionSide = ActionSide.Undefined;
            switch (strategyResult)
            {
            case StrategyResult.Long: openingOrderActionSide = ActionSide.Buy; break;
            case StrategyResult.Short: openingOrderActionSide = ActionSide.Sell; break;
            default: Trace.Fail("Invalid StrategyResult"); break;
            }

            int openingOrderQuantity = 1;
            switch (priceBarsAndIndicatorValues.Contract.SecurityType)
            {
            case SecurityType.Stock:
            openingOrderQuantity = UtilFunction.CalculateQuantityToTrade(
               StockDesiredPositionSizeInDollars, 
               priceBarsAndIndicatorValues.PriceBars[0].Close);
            break;
            case SecurityType.Future:
            if (priceBarsAndIndicatorValues.FullSymbol.StartsWith("YM"))
            {
               openingOrderQuantity = YMNumberOfContractsToTrade;
            }
            else if (priceBarsAndIndicatorValues.FullSymbol.StartsWith("ES"))
            {
               openingOrderQuantity = ESNumberOfContractsToTrade;
            }
            break;
            }
            
            Order openingOrder = new Order()
            {
               OrderType = OrderType.Market,
               TotalQuantity = openingOrderQuantity,
               ActionSide = openingOrderActionSide,
               Flavor = OrderFlavor.Opening
            };
            Contract contract = priceBarsAndIndicatorValues.Contract;

            IBClientWrapper.SubmitOrder(openingOrder, contract);
            IncrementTradeCountOnRealTimeBarsListView(contract.FullSymbol);
         }
      }

      private StrategyResult ApplyStrategyLogic(
         PriceBarsAndIndicatorValues priceBarsAndIndicatorValues)
      {
         foreach (Func<PriceBarsAndIndicatorValues, StrategyResult>
            tradingStrategy in _tradingStrategies)
         {
            StrategyResult strategyResult = tradingStrategy(priceBarsAndIndicatorValues);
            if (strategyResult != StrategyResult.NoTrade)
            {
               return strategyResult;
            }
         }
         return StrategyResult.NoTrade;
      }

      bool HaveOpenDayTradeOrderWith(string fullSymbol)
      {
         if (_symbolsWithOpenDaytradeOrders.Contains(fullSymbol))
         {
            return true;
         }
         else
         {
            return false;
         }
      }

      void client_HistoricalPriceBarReceived(object sender, HistoricalDataEventArgs e)
      {
         PriceBarsAndIndicatorValues priceBarsAndIndicatorValues =
            _backfillRequestIdToPriceBarsAndIndicatorValues[e.RequestId];
         PriceBar backFillBar = new PriceBar(e.Open, e.High, e.Low, e.Close,
            e.Volume, e.OpenTimeSince1970, e.Date);
         priceBarsAndIndicatorValues.PushFrontBackFillBar(backFillBar);

         // Verification Purposes
         if (e.RecordNumber == 1)
         {
            textBoxTesting1.WriteLine("======" +
               priceBarsAndIndicatorValues.FullSymbol + " Backfill======");
         }
         textBoxTesting1.WriteLine(e.Date.ToLongTimeString()
            + " Record " + e.RecordNumber + " of " + e.RecordTotal);
         Debug.Assert(!e.HasGaps);

         if (e.RecordNumber == e.RecordTotal)
         {
            _backfillCompleted.Set();
            _backfillRequestIdToPriceBarsAndIndicatorValues.Remove(e.RequestId);
         }
      }

      void UpdateRealTimeBarsListView(
         PriceBarsAndIndicatorValues priceBarsAndIndicatorValues, RealTimeBarEventArgs e)
      {
         string fullSymbol = priceBarsAndIndicatorValues.FullSymbol;
         IncrementListViewBarsReceivedByOne(fullSymbol);
         UpdateListViewLastPrice(fullSymbol, e.Close);
      }

      void client_OpenOrderReceived(object sender, OpenOrderEventArgs e)
      {
         OrderAndContract orderAndContract = new OrderAndContract(e.Order, e.Contract);

         // Make available this OrderAndContract to client_OrderStatusReceived()
         _orderIdToOrderAndContract.Upsert(e.OrderId, orderAndContract);

         // Testing purposes
         textBoxTesting2.WriteLine("OpenOrderReceived: ",
            e.Order.OrderType.ToString(),
            " ", e.OrderState.OrderStatus.ToString(),
            " ", e.OrderId.ToString());

         _symbolsWithOpenDaytradeOrders.Add(e.Contract.FullSymbol);
      }

      static bool OrderIsAnOpeningOrder(Order order)
      {
         if (String.IsNullOrEmpty(order.OneCancelsAllGroup)) return true;
         else return false;
      }

      void client_OrderStatusReceived(object sender, OrderStatusEventArgs e)
      {
         OrderAndContract orderAndContract;
         _orderIdToOrderAndContract.TryGetValue(e.OrderId, out orderAndContract);

         // Testing purposes
         if (orderAndContract != null)
         {
            textBoxTesting2.WriteLine("OrderStatusReceived: ",
               orderAndContract.Order.OrderType.ToString(),
               " ", e.OrderStatus.ToString(),
               " ", e.OrderId.ToString());
         }
         else
         {
            textBoxTesting2.WriteLine("OrderStatusReceived: ",
               " ", e.OrderStatus.ToString(),
               " ", e.OrderId.ToString());
            return;
         }

         switch (e.OrderStatus)
         {
         case OrderStatus.Canceled:
         {
            DeregisterOrderAndContractFromDayTrading(orderAndContract);
            break;
         }
         case OrderStatus.Filled:
         {
            if (!OrderIsAnOpeningOrder(orderAndContract.Order))
            {
               DeregisterOrderAndContractFromDayTrading(orderAndContract);
            }
            break;
         }
         }
      }

      void client_PortfolioUpdated(object sender, PortfolioEventArgs e)
      {
         if (e.Position != 0)
         {
            UpdatePortfolioListView(e.Contract.FullSymbol, e.Position);
         }
         else
         {
            RemoveFromPortfolioListView(e.Contract.FullSymbol);
         }
      }

      void client_AccountValueUpdated(object sender, AccountValueEventArgs e)
      {
         switch (e.Key)
         {
         case "AvailableFunds":
         case "NetLiquidationByCurrency":
         case "GrossPositionValue":
         case "FullInitMarginReq":
         case "EquityWithLoanValue":
         case "BuyingPower":
         case "CashBalance":
         case "Cushion":
         case "ExcessLiquidity":
         case "InitMarginReq":
         case "Leverage-S":
         case "LookAheadAvailableFunds":
         case "LookAheadExcessLiquidity":
         case "LookAheadInitMarginReq":
         case "MainMarginReq":
         case "NetLiquidation":
         case "PreviousDayEquityWithLoanValue":
         case "RealizedPnL":
         case "UnrealizedPnL":
         {
            UpsertAccountValueDictionaryAndListView(e.Key, e.Value);
            break;
         }
         }
      }

      // Time is UTC +7 relative to PST time
      void client_CurrentServerTimeReceived(object sender, CurrentServerTimeEventArgs e)
      {
         textBoxMessages.WriteLine("Current time: ", e.ServerTime.ToString());
      }

      void client_AccountTimeReceived(object sender, AccountTimeEventArgs e)
      {
         textBoxMessages.WriteLine("Account Time: ", e.Timestamp);
      }

      void client_ErrorOccurred(object sender, ErrorEventArgs e)
      {
         if (textBoxMessages.InvokeRequired)
         {
            textBoxMessages.BeginInvoke((MethodInvoker)delegate
            {
               client_ErrorOccurred(this, e);
            });
            return;
         }

         switch ((int)e.ErrorCode)
         {
         // Ignore 'tried to cancel an order in a non-cancellable state' message
         case 161:
         // Ignore connection status messages
         case 2103:
         case 2104:
         case 2105:
         case 2106:
         case 2107:
         // Ignore 'your order will not be placed at the exchange until' message
         case 399:
         // Ignore 'outside regular trading hours' message
         case 2109:
         {
            break;
         }

         // Pacing violation
         case 100:
         {
            textBoxMessages.WriteLine("Error: Pacing violation. Exceeded 50 messages/second maximum.");
            break;
         }

         // Duplicate order id
         case 103:
         {
            int orderId = e.Id;
            textBoxMessages.WriteLine("Duplicate order id ", orderId.ToString(), ".");
            break;
         }

         // Symbol not found
         case 200:
         {
            int requestId = e.Id;
            if (RealTimeBarSubscriptionCausedSymbolNotFoundError(requestId))
            {
               string fullSymbol = _requestIdToPriceBarsAndIndicatorValues[requestId].FullSymbol;
               IBClientWrapper.UnsubscribeFromRealTimeBars(fullSymbol, true);
               textBoxMessages.WriteLine("Error 200: No security found for symbol ", fullSymbol);
            }
            else
            {
               textBoxMessages.WriteLine("Error 200: No security found for symbol submitted.");
            }
            break;
         }

         // Order cancelled
         case 202:
         {
            //if (e.ErrorMessage == "Order Canceled - reason:")
            //{
            //   int orderId = e.Id;
            //   OrderAndContract orderAndContract = _orderIdToOrderAndContract[orderId];
            //   DeregisterOrderAndContractFromDayTrading(orderAndContract);
            //}
            break;
         }

         // Invalid duration for historical data request
         case 321:
         {
            textBoxMessages.WriteLine("Error 321: Invalid duration for historical data request.");
            break;
         }

         // Unable to short.
         case 404:
         {
            //int orderId = e.Id;
            //OrderAndContract orderAndContract = _orderIdToOrderAndContract[orderId];
            //string fullSymbol = orderAndContract.Contract.FullSymbol;

            //textBoxMessages.WriteLine("Error 404: Unable to short symbol ", fullSymbol);
            //ibClient.CancelOrder(orderId);
            //ClientWrapper.Unsubscribe(symbol, UnsubscribeType.UnableToShortSymbol);
            //DeregisterOrderAndContractFromDayTrading(orderAndContract);
            break;
         }

         default:
         {
            textBoxMessages.WriteLine("Error ", e.ErrorCode.ToString(), " ",
               e.ErrorMessage, ". Id = ", e.Id.ToString());
            break;
         }
         };
      }

      private bool RealTimeBarSubscriptionCausedSymbolNotFoundError(int requestId)
      {
         if (_requestIdToPriceBarsAndIndicatorValues.ContainsKey(requestId))
         {
            return true;
         }
         else
         {
            return false;
         }
      }

      private void client_NextValidOrderIdReceived(object sender, NextValidIdEventArgs e)
      {
         _currentOrderId = e.OrderId;
      }
   }
}
